package com.tencent.supersonic.headless.core.translator.calcite.schema;

import java.util.List;

import org.apache.calcite.avatica.util.TimeUnitRange;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
import org.apache.calcite.sql.SqlAbstractDateTimeLiteral;
import org.apache.calcite.sql.SqlAlienSystemTypeNameSpec;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlDataTypeSpec;
import org.apache.calcite.sql.SqlDateLiteral;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.SqlIntervalLiteral;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlTimeLiteral;
import org.apache.calcite.sql.SqlTimestampLiteral;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.SqlDialect.Context;
import org.apache.calcite.sql.fun.SqlFloorFunction;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlConformance;
import org.checkerframework.checker.nullness.qual.Nullable;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

public class OracleSqlDialect extends SqlDialect {

	private static final SqlConformance tagTdwSqlConformance = new OracleSqlConformance();

	private static final RelDataTypeSystem ORACLE_TYPE_SYSTEM = new RelDataTypeSystemImpl() {
		@Override
		public int getMaxPrecision(SqlTypeName typeName) {
			switch (typeName) {
			case VARCHAR:
				// Maximum size of 4000 bytes for varchar2.
				return 4000;
			default:
				return super.getMaxPrecision(typeName);
			}
		}
	};

	private final int majorVersion;

	/** Creates an OracleSqlDialect. */
	public OracleSqlDialect(Context context) {
		super(context);
		this.majorVersion = context.databaseMajorVersion();
	}

	public static void unparseFetchUsingAnsi(SqlWriter writer, @Nullable SqlNode offset, @Nullable SqlNode fetch) {
		/*
		 * Preconditions.checkArgument(fetch != null || offset != null); SqlWriter.Frame
		 * fetchFrame; writer.newlineAndIndent(); fetchFrame =
		 * writer.startList(SqlWriter.FrameTypeEnum.OFFSET); writer.keyword("LIMIT");
		 * boolean hasOffset = false; if (offset != null) { offset.unparse(writer, -1,
		 * -1); hasOffset = true; }
		 * 
		 * if (fetch != null) { if (hasOffset) { writer.keyword(","); }
		 * fetch.unparse(writer, -1, -1); }
		 * 
		 * writer.endList(fetchFrame);
		 */

	}

	@Override
	public void quoteStringLiteralUnicode(StringBuilder buf, String val) {
		buf.append("'");
		buf.append(val);
		buf.append("'");
	}

	@Override
	public void quoteStringLiteral(StringBuilder buf, String charsetName, String val) {
		buf.append(literalQuoteString);
		buf.append(val.replace(literalEndQuoteString, literalEscapedQuote));
		buf.append(literalEndQuoteString);
	}

	@Override
	public boolean supportsApproxCountDistinct() {
		return true;
	}

	@Override
	public boolean supportsCharSet() {
		return false;
	}

	@Override
	public boolean supportsDataType(RelDataType type) {
		switch (type.getSqlTypeName()) {
		case BOOLEAN:
			return false;
		default:
			return super.supportsDataType(type);
		}
	}

	@Override
	public @Nullable SqlNode getCastSpec(RelDataType type) {
		String castSpec;
		switch (type.getSqlTypeName()) {
		case SMALLINT:
			castSpec = "NUMBER(5)";
			break;
		case INTEGER:
			castSpec = "NUMBER(10)";
			break;
		case BIGINT:
			castSpec = "NUMBER(19)";
			break;
		case DOUBLE:
			castSpec = "DOUBLE PRECISION";
			break;
		default:
			return super.getCastSpec(type);
		}

		return new SqlDataTypeSpec(new SqlAlienSystemTypeNameSpec(castSpec, type.getSqlTypeName(), SqlParserPos.ZERO),
				SqlParserPos.ZERO);
	}

	@Override
	protected boolean allowsAs() {
		return false;
	}

	@Override
	public boolean supportsAliasedValues() {
		return false;
	}

	@Override
	public SqlConformance getConformance() {
		// mysql_5
		return tagTdwSqlConformance;
	}

	public boolean supportsGroupByWithCube() {
		return true;
	}

	@Override
	public void unparseDateTimeLiteral(SqlWriter writer, SqlAbstractDateTimeLiteral literal, int leftPrec,
			int rightPrec) {
		if (literal instanceof SqlTimestampLiteral) {
			writer.literal("TO_TIMESTAMP('" + literal.toFormattedString() + "', 'YYYY-MM-DD HH24:MI:SS.FF')");
		} else if (literal instanceof SqlDateLiteral) {
			writer.literal("TO_DATE('" + literal.toFormattedString() + "', 'YYYY-MM-DD')");
		} else if (literal instanceof SqlTimeLiteral) {
			writer.literal("TO_TIME('" + literal.toFormattedString() + "', 'HH24:MI:SS.FF')");
		} else {
			super.unparseDateTimeLiteral(writer, literal, leftPrec, rightPrec);
		}
	}

	@Override
	public List<String> getSingleRowTableName() {
		return ImmutableList.of("DUAL");
	}

	@Override
	public void unparseCall(SqlWriter writer, SqlCall call, int leftPrec, int rightPrec) {
		if (call.getOperator() == SqlStdOperatorTable.SUBSTRING) {
			SqlUtil.unparseFunctionSyntax(SqlLibraryOperators.SUBSTR_ORACLE, writer, call, false);
		} else {
			switch (call.getKind()) {
			case POSITION:
				final SqlWriter.Frame frame = writer.startFunCall("INSTR");
				writer.sep(",");
				call.operand(1).unparse(writer, leftPrec, rightPrec);
				writer.sep(",");
				call.operand(0).unparse(writer, leftPrec, rightPrec);
				if (3 == call.operandCount()) {
					writer.sep(",");
					call.operand(2).unparse(writer, leftPrec, rightPrec);
				}
				if (4 == call.operandCount()) {
					writer.sep(",");
					call.operand(2).unparse(writer, leftPrec, rightPrec);
					writer.sep(",");
					call.operand(3).unparse(writer, leftPrec, rightPrec);
				}
				writer.endFunCall(frame);
				break;
			case FLOOR:
				if (call.operandCount() != 2) {
					super.unparseCall(writer, call, leftPrec, rightPrec);
					return;
				}

				final SqlLiteral timeUnitNode = call.operand(1);
				final TimeUnitRange timeUnit = timeUnitNode.getValueAs(TimeUnitRange.class);

				SqlCall call2 = SqlFloorFunction.replaceTimeUnitOperand(call, timeUnit.name(),
						timeUnitNode.getParserPosition());
				SqlFloorFunction.unparseDatetimeFunction(writer, call2, "TRUNC", true);
				break;

			default:
				super.unparseCall(writer, call, leftPrec, rightPrec);
			}
		}
	}

	@Override
	public void unparseOffsetFetch(SqlWriter writer, @Nullable SqlNode offset, @Nullable SqlNode fetch) {
		// majorVersion in SqlDialect.EMPTY_CONTEXT is -1 by default
		if (this.majorVersion != -1 && this.majorVersion < 12) {
			throw new RuntimeException("Lower Oracle version(<12) doesn't support offset/fetch syntax!");
		}
		super.unparseOffsetFetch(writer, offset, fetch);
	}
}
